<?php

	/**
	 * @see Bike_Initial
	 */
	require_once 'Bike/Initial.php';
	
	/**
	 * @see Zend_Loader
	 */
	require_once 'Zend/Loader.php';
	
	/**
	 * @see Zend_Reflection_Class
	 */
	require_once 'Zend/Reflection/Class.php';
	
	/**
	 * @see Zend_Controller_Front
	 */
	require_once 'Zend/Controller/Front.php';
	
	/**
	 * @see Zend_Json
	 */
	require_once 'Zend/Json.php';
	
	
	class Bike_Js extends Bike_Initial
	{
		static protected $_instance;
		static protected $_factoryCallback = array(__CLASS__, 'factory');
		static protected $_objectInstances = array();
		
		static protected $_debug = False;
		static protected $_useCaching = True;
		
		static protected $_classNames = array();
		static protected $_classNameAliases;		

		static public function getInstance()
		{			if(is_null(self::$_instance))
			{				self::$_instance = new self();
			}
			return self::$_instance;
		}
		
		static public function getUseCaching()
		{
			return self::$_useCaching;
		}
		
		static public function setUseCaching($caching)
		{
			self::$_useCaching = $caching;
		}
		
		static public function getFactoryCallback()
		{
			return self::$_factoryCallback;
		}
		
		static public function getObjectInstances()
		{
			return self::$_objectInstances;
		}
		
		static protected function addObjectInstance($name, $objectInstance = False)
		{
			self::$_objectInstances[$name] = $objectInstance;
		}
		
		static public function getObjectInstance($name)
		{
			if(self::getUseCaching())
			{
				
				$instances = self::getObjectInstances();
				if(!isset($instances[$name]))
				{
					self::addObjectInstance($name);
					$instances[$name] = call_user_func(self::getFactoryCallback(), $name);
					self::addObjectInstance($name, $instances[$name]);
				}
				if($instances[$name] instanceof Bike_Js_Interface_Js)
				{
					return $instances[$name];
				}
			}
			else
			{
				$Object = call_user_func(self::getFactoryCallback(), $name);
				if($Object instanceof Bike_Js_Interface_Js)
				{
					return $Object;
				}
			}
			require_once 'Bike/Js/Exception.php';
			throw new Bike_Js_Exception(sprintf('There are no instances for "%s"', $name));
		}
		
		static public function getClonedObjectInstance($name)
		{
			return clone self::getObjectInstance($name);
		}
		
		static protected function getDefaultClassNameAliases()
		{
			return array(
				'Array' => 'Arr',
				'Function' => 'Func',
				'Ex' => 'Expression',
				'Var' => 'Variable',
			);
		}
		
		static protected function getClassNameAliases()
		{
			if(is_null(self::$_classNameAliases))
			{
				self::setClassNameAliases();
			}
			return self::$_classNameAliases;
		}
		
		static protected function setClassNameAliases(array $classNameAliases=array())
		{
			self::$_classNameAliases = array_merge((array) self::getDefaultClassNameAliases(), (array) self::$_classNameAliases, (array) $classNameAliases);
		}
		
		static public function addClassNameAlias($classNameAlias, $className)
		{
			self::setClassNameAliases(array($classNameAlias => $className));
		}
		
		static public function addClassNameAliases(array $classNameAliases)
		{
			self::setClassNameAliases($classNameAliases);
		}
		
		static public function removeClassNameAlias($classNameAlias)
		{
			$aliases = self::getClassNameAliases();
			if(isset($aliases[$classNameAlias]))
			{
				unset(self::$_classNameAliases[$classNameAlias]);
				self::setClassNameAliases();
			}
			else
			{
				require_once 'Bike/Js/Exception.php';
				throw new Bike_Js_Exception(sprintf('Invalid class name alias "%s"', $classNameAlias));
			}
		}
		
		static protected function getClassNameAlias($className)
		{
			$aliases = self::getClassNameAliases();
			return (isset($aliases[$className]) ? $aliases[$className] : $className);
		}

		static public function getClassName($Name)
		{			$Name = self::getClassNameAlias($Name);
			if(!array_key_exists($Name, self::$_classNames))
			{
				self::$_classNames[$Name] = False;
				$className = '';
				$_className = __CLASS__.'_'.$Name;
				try
				{					Zend_Loader::loadClass($_className);
					$className = $_className;
				}
				catch(Zend_Exception $e)
				{					try
					{						@Zend_Loader::loadClass($Name);
						$className = $Name;
					}
					catch(Zend_Exception $e)
					{						require_once 'Bike/Js/Exception.php';
						throw new Bike_Js_Exception('Class "'.$_className.'" or "'.$Name.'" is undefined');
					}
				}
				$Reflection = new Zend_Reflection_Class($className);
				if($className != $Reflection->getName() || $Reflection->isAbstract())
				{					require_once 'Bike/Js/Exception.php';
					throw new Bike_Js_Exception('Class "'.$className.'" is undefined');
				}
				elseif(!$Reflection->implementsInterface('Bike_Js_Interface_Js'))
				{					require_once 'Bike/Js/Exception.php';
					throw new Bike_Js_Exception($className.' must be instance of Bike_Js_Interface_Js');
				}
				self::$_classNames[$Name] = $className;
			}
			if(!self::$_classNames[$Name])
			{				require_once 'Bike/Js/Exception.php';
				throw new Bike_Js_Exception('Class "'.$Name.'" is undefined or is not instance of Bike_Js_Interface');
			}
			return self::$_classNames[$Name];
		}

		static public function factory()
		{			$args = func_get_args();
			$name = array_shift($args);
			if(!$name)
			{				require_once 'Bike/Js/Exception.php';
				throw new Bike_Js_Exception('Name of class/subclass is undefined');
			}
			$className = self::getClassName($name);
			$Object = new $className();
			
			if($args)
			{
				$Object->setArgsOptions($args);
			}
			return $Object;
		}
		
		static public function factoryGlue()
		{
			$args = func_get_args();
			$className = self::getClassName('Glue');
			return call_user_func_array(array($className, 'factory'), $args);
		}

		static public function setDebug($Debug=True)
		{
			self::$_debug = (bool) $Debug;
			return self::getInstance();
		}

		static public function isDebug()
		{
			return (bool) self::$_debug;
		}

		static public function isDisplayExceptions()
		{
			return (bool) Zend_Controller_Front::getInstance()->getParam('displayExceptions');
		}

		static public function jsonEncode($Value, $CycleCheck = False/*, array $Options=array('enableJsonExprFinder' => True)*/)
		{
			if(is_object($Value))
			{
				$json = '';
				foreach($Value as $name => $value)
				{
					$json .= (!$json ? '' : ',').Zend_Json::encode($name).':'.$value;
				}				
				return '{'.$json.'}';
			}
			if(is_array($Value))
			{
				$jsonArr = array();
				$assocFlag = False;
				$json = '';
				foreach($Value as $name => $value)
				{
					if(!is_int($name))
					{
						$assocFlag = True;
					}
					$json .= (!$json ? '' : ',').Zend_Json::encode((string) $name).':'.$value;
				}
				
				if($assocFlag)
				{
					return '{'.$json.'}';
				}
				
				return '['.join(',', $Value).']';
			}
			
			return Zend_Json::encode($Value, $CycleCheck/*, $Options*/);
		}

		public function __construct()
		{			if(self::$_instance instanceof self)
			{				require_once 'Bike/Js/Exception.php';
				throw new Bike_Js_Exception('Use '.__CLASS__.'::getInstance()');
			}
		}

		public function __call($MethodName, array $Args=null)
		{			try
			{				return $this->getClonedObjectInstance($MethodName)->setArgsOptions($Args);
				/*array_unshift($Args, $MethodName);
				return call_user_func_array($this->getFactoryCallback(), $Args);*/
			}
			catch(Bike_Js_Exception $e)
			{
				return call_user_func_array(array($this->getClonedObjectInstance('Variable'), $MethodName), $Args);
				/*$obj = call_user_func_array($this->getFactoryCallback(), array('Variable'));
				array_shift($Args);
				return call_user_func_array(array($obj, $MethodName), $Args);*/
			}
		}

		public function __get($Name)
		{
			try
			{
				return $this->getClonedObjectInstance('ClassName')->setArgsOptions(array($Name));
			}
			catch(Bike_Exception $e)
			{				return $this->getClonedObjectInstance('Variable')->$Name;
				//return $this->getClonedObjectInstance('Variable')->setArgsOptions(array($Name));
			}
		}

		public function __toString()
		{			return '';
		}

		public function toJson()
		{			return $this->__toString();
		}
	}
